package com.lx.idgenerator.segement;

import com.lx.idgenerator.base.model.IDWrapper;
import com.lx.idgenerator.exception.BizException;
import com.lx.idgenerator.segement.bus.FillSegmentEvent;
import com.lx.idgenerator.segement.bus.FillSegmentUpdater;
import com.lx.idgenerator.segement.leaf.ILeafInfoService;
import com.lx.idgenerator.segement.policy.IFetchPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author lX
 * @version JDK 8
 * @className SegmentBuffer (此处以class为例)
 * @date 2024/10/28
 * @description TODO
 */
public class SegmentBuffer {

    Logger logger = LoggerFactory.getLogger(SegmentBuffer.class);
    /**
     * 状态
     * <p>
     * CAS 替换状态
     */
    private static final int CHANGE_NEXT_BUFFER = 3;
    /**
     * 填充完毕
     */
    private static final int FILLED_NEXT_BUFFER = 2;
    /**
     * 正在填充
     */
    private static final int FILLING_NEXT_BUFFER = 1;
    /**
     * 正常使用状态
     */
    public static final int NORMAL = 0;

    /**
     * 初始状态
     */
    private volatile int state = NORMAL;

    /**
     * 当前段缓存
     */
    private volatile Segment curSegment;

    /**
     * change段缓存
     */
    private volatile Segment changeSegment;


    /**
     * 交互层  redis/Mysql/Etcd
     */
    private ILeafInfoService leafInfoService;

    /**
     * state  偏移量
     */
    private static final long STATE_OFFSET;

    private static final Unsafe UNSAFE;

    /**
     * segment 更新策略
     */
    private FillSegmentUpdater fillSegmentUpdater;


    /**
     * 拉取策略
     */
    private IFetchPolicy fetchPolicy;

    /**
     * 业务标识Key
     */
    private final String tag;

    /**
     * 异常信息
     */
    private Throwable e;

    private static final String THE_UNSAFE = "theUnsafe";

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField(THE_UNSAFE);
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
            STATE_OFFSET = UNSAFE.objectFieldOffset(SegmentBuffer.class.getDeclaredField("state"));
        } catch (Exception e) {
            throw new BizException("Failed to initialize Unsafe", e);
        }
    }

    /**
     * @param leafInfoService
     * @param fillSegmentUpdater
     * @param iFetchPolicy
     * @param tag
     */
    public SegmentBuffer(ILeafInfoService leafInfoService, FillSegmentUpdater fillSegmentUpdater, IFetchPolicy iFetchPolicy, String tag) {
        this.leafInfoService = leafInfoService;
        this.fillSegmentUpdater = fillSegmentUpdater;
        this.fetchPolicy = iFetchPolicy;
        this.tag = tag;
        //初始化一个空的
        init();
    }


    private void init() {
        //初始化一个空的
        curSegment = new Segment();
    }

    public void setChangeSegment(Segment changeSegment) {
        this.changeSegment = changeSegment;
    }

    /**
     * 根据size获取下一个ID
     *
     * @param size
     * @return
     */
    public IDWrapper nextID(int size) {

        //填充时可能发生异常,此时如果填充失败不允许再从缓存中获取值
        checkException();

        //检查是否需要填充
        checkSegmentNeedFill();

        IDWrapper idWrapper;

        for (; ; ) {
            //返回不为空就是不需要扩容
            idWrapper = curSegment.nextID(size);

            if (null != idWrapper) {
                return idWrapper;
            }
            //再检查一次 如果此时当前线程为最后一个要获取ID的线程,分段ID刚好使用完毕,不再检查一次的话 会进入死循环中直到
            //其他线程来发起填充更新,在这里检查的目的就是如果没有其他线程发起的话，自己进行发起。
            checkSegmentNeedFill();
            for (; ; ) {
                //如果其他线程已经填充好了,结束这一层循环进入上一层循环重新从curSegment拿
                if (state == NORMAL) {
                    break;
                }
                //这里如果完成填充,会唤醒等待中的一个线程,一个线程把当前buffer状态改为填充完成,并把两个Segment对象互换,并唤醒所有等待的线程
                if (state == FILLED_NEXT_BUFFER && changeSegment != null && compareAndSwapState(FILLED_NEXT_BUFFER, CHANGE_NEXT_BUFFER)) {
                    logger.info("{}:fill finish", tag);
                    changeSegmentAndNotify();
                    break;
                }
                //如果填充中,等待填充完成
                synchronized (this) {
                    try {
                        //等待填充  其他没抢到的线程等待
                        this.wait();
                    } catch (InterruptedException e) {
                        logger.error("Interrupted while waiting for segment to be filled", e);
                        Thread.currentThread().interrupt(); // Restore interrupted status
                        throw new BizException("Interrupted while waiting for segment to be filled", e);
                    }
                }


            }

        }


    }

    /**
     * 交换buffer
     */
    private void changeSegmentAndNotify() {
        //交换buffer
        curSegment = changeSegment;
        changeSegment = null;
        state = NORMAL;
        synchronized (this) {
            //唤醒所有等待线程
            this.notifyAll();
        }
    }

    private boolean compareAndSwapState(int curState, int newState) {
        return UNSAFE.compareAndSwapInt(this, STATE_OFFSET, curState, newState);
    }

    /**
     * 检查是否有异常
     */
    private void checkException() {
        if (e != null) {
            if (e instanceof BizException) {
                throw (BizException) e;
            } else {
                throw new BizException("检测到异常,tag{" + tag + "}", e);
            }
        }
    }


    /**
     * 检查Segment是否需要填充
     */
    private void checkSegmentNeedFill() {
        //如果填充比例达到了阈值 那么就需要扩容  多线程下通过cas保证只有一个线程进行填充任务
        if (curSegment.usedPercent().compareTo(fetchPolicy.nextSegFetchPercent(this.tag)) >= 0
                && null == changeSegment && compareAndSwapState(NORMAL, FILLING_NEXT_BUFFER)) {
            createUpdateEventAndPublish();
        }
    }

    /**
     * 创建更新事件并且进行发布
     */
    private void createUpdateEventAndPublish() {
        logger.info("业务key:{}:发布事件进行填充分段对象", tag);
        //创建填充事件
        FillSegmentEvent fillSegmentEvent = new FillSegmentEvent(this, leafInfoService, fetchPolicy.segmentFetchSize(tag), tag);
        //发布事件
        fillSegmentUpdater.publishEvent(fillSegmentEvent);

    }

    /**
     * 填充完成
     * @param e
     */
    public void fillComplete(Throwable e) {
        this.e = e;
        //完场填充
        state = FILLED_NEXT_BUFFER;
        synchronized (this) {
            this.notify();
        }
    }


}
